﻿using System;
using Autofac.Core;
using Autofac.Core.Registration;
using Autofac.Features.ResolveAnything;
using Microsoft.Practices.Prism;
using Microsoft.Practices.Prism.Events;
using Microsoft.Practices.Prism.Logging;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.ServiceLocation;

namespace Autofac.Integration.Prism
{
	/// <summary>
	/// Base class that provides a basic bootstrapping sequence that
	/// registers most of the Composite Application Library assets
	/// in a <see cref="IContainer"/>.
	/// </summary>
	/// <remarks>
	/// This class must be overriden to provide application specific configuration.
	/// </remarks>
	public abstract class AutofacBootstrapper : Bootstrapper
	{
		bool _useDefaultConfiguration = true;

		/// <summary>
		/// Gets the default <see cref="IContainer"/> for the application.
		/// </summary>
		/// <value>The default <see cref="IContainer"/> instance.</value>
		public IContainer Container { get; protected set; }

		/// <summary>
		/// Run the bootstrapper process.
		/// </summary>
		/// <param name="runWithDefaultConfiguration">If <see langword="true"/>, registers default Composite Application Library services in the container. This is the default behavior.</param>
		public override void Run(bool runWithDefaultConfiguration)
		{
			_useDefaultConfiguration = runWithDefaultConfiguration;

			Logger = CreateLogger();
			if (Logger == null)
			{
				throw new InvalidOperationException(AutofacBootstrapperResources.NullLoggerFacadeException);
			}

			Logger.Log(AutofacBootstrapperResources.LoggerCreatedSuccessfully, Category.Debug, Priority.Low);

			Logger.Log(AutofacBootstrapperResources.CreatingModuleCatalog, Category.Debug, Priority.Low);
			ModuleCatalog = CreateModuleCatalog();
			if (ModuleCatalog == null)
			{
				throw new InvalidOperationException(AutofacBootstrapperResources.NullModuleCatalogException);
			}

			Logger.Log(AutofacBootstrapperResources.ConfiguringModuleCatalog, Category.Debug, Priority.Low);
			ConfigureModuleCatalog();

			ContainerBuilder builder;

			Logger.Log(AutofacBootstrapperResources.CreatingAutofacContainerBuilder, Category.Debug, Priority.Low);
			builder = CreateBuilder();
			if (builder == null)
			{
				throw new InvalidOperationException(AutofacBootstrapperResources.NullAutofacContainerBuilderException);
			}

			Logger.Log(AutofacBootstrapperResources.ConfiguringAutofacContainerBuilder, Category.Debug, Priority.Low);
			ConfigureBuilder(builder);

			Logger.Log(AutofacBootstrapperResources.CreatingAutofacContainer, Category.Debug, Priority.Low);
			Container = CreateContainer(builder);
			if (Container == null)
			{
				throw new InvalidOperationException(AutofacBootstrapperResources.NullAutofacContainerException);
			}

			Logger.Log(AutofacBootstrapperResources.ConfiguringAutofacContainer, Category.Debug, Priority.Low);
			ConfigureContainer();

			// This is required as Prism makes the assumption that a concrete class will always be
			// available from the container even if not specifically registered.
			// e.g. These lines in the base Prism Bootstrapper class
			//
			// regionAdapterMappings.RegisterMapping(typeof(Selector), ServiceLocator.Current.GetInstance<SelectorRegionAdapter>());
			// regionAdapterMappings.RegisterMapping(typeof(ItemsControl), ServiceLocator.Current.GetInstance<ItemsControlRegionAdapter>());
			// regionAdapterMappings.RegisterMapping(typeof(ContentControl), ServiceLocator.Current.GetInstance<ContentControlRegionAdapter>());
			//
			Container.Configure(b => b.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource()));

			Logger.Log(AutofacBootstrapperResources.ConfiguringServiceLocatorSingleton, Category.Debug, Priority.Low);
			ConfigureServiceLocator();

			Logger.Log(AutofacBootstrapperResources.ConfiguringRegionAdapters, Category.Debug, Priority.Low);
			ConfigureRegionAdapterMappings();

			Logger.Log(AutofacBootstrapperResources.ConfiguringDefaultRegionBehaviors, Category.Debug, Priority.Low);
			ConfigureDefaultRegionBehaviors();

			Logger.Log(AutofacBootstrapperResources.RegisteringFrameworkExceptionTypes, Category.Debug, Priority.Low);
			RegisterFrameworkExceptionTypes();

			Logger.Log(AutofacBootstrapperResources.CreatingShell, Category.Debug, Priority.Low);
			Shell = CreateShell();
			if (Shell != null)
			{
				Logger.Log(AutofacBootstrapperResources.SettingTheRegionManager, Category.Debug, Priority.Low);
				RegionManager.SetRegionManager(Shell, Container.Resolve<IRegionManager>());

				Logger.Log(AutofacBootstrapperResources.UpdatingRegions, Category.Debug, Priority.Low);
				RegionManager.UpdateRegions();

				Logger.Log(AutofacBootstrapperResources.InitializingShell, Category.Debug, Priority.Low);
				InitializeShell();
			}

			if (Container.IsRegistered<IModuleManager>())
			{
				Logger.Log(AutofacBootstrapperResources.InitializingModules, Category.Debug, Priority.Low);
				InitializeModules();
			}

			Logger.Log(AutofacBootstrapperResources.BootstrapperSequenceCompleted, Category.Debug, Priority.Low);
		}

		/// <summary>
		/// Configures the LocatorProvider for the <see cref="ServiceLocator" />.
		/// </summary>
		protected override void ConfigureServiceLocator()
		{
			ServiceLocator.SetLocatorProvider(() => Container.Resolve<IServiceLocator>());
		}

		/// <summary>
		/// Registers in the <see cref="IContainer"/> the <see cref="Type"/> of the Exceptions
		/// that are not considered root exceptions by the <see cref="ExceptionExtensions"/>.
		/// </summary>
		protected override void RegisterFrameworkExceptionTypes()
		{
			base.RegisterFrameworkExceptionTypes();

			ExceptionExtensions.RegisterFrameworkExceptionType(typeof(DependencyResolutionException));
			ExceptionExtensions.RegisterFrameworkExceptionType(typeof(ComponentNotRegisteredException));
		}

		/// <summary>
		/// Creates the <see cref="ContainerBuilder"/> that will be used to create the default container.
		/// </summary>
		/// <returns>A new instance of <see cref="ContainerBuilder"/>.</returns>
		protected virtual ContainerBuilder CreateBuilder()
		{
			return new ContainerBuilder();
		}

		/// <summary>
		/// Configures the <see cref="ContainerBuilder"/>. May be overwritten in a derived class to add specific
		/// type mappings required by the application.
		/// </summary>
		protected virtual void ConfigureBuilder(ContainerBuilder builder)
		{
		}

		/// <summary>
		/// Creates the <see cref="IContainer"/> that will be used as the default container.
		/// </summary>
		/// <returns>A new instance of <see cref="IContainer"/>.</returns>
		protected virtual IContainer CreateContainer(ContainerBuilder builder)
		{
			var result = builder.Build();

			// Register self as instance of IContainer
			result.RegisterInstance(result);

			return result;
		}

		/// <summary>
		/// Configures the <see cref="IContainer"/>. May be overwritten in a derived class to add specific
		/// type mappings required by the application.
		/// </summary>
		protected virtual void ConfigureContainer()
		{
    		Container.RegisterInstance(Logger);
    		Container.RegisterInstance(ModuleCatalog);

			if (_useDefaultConfiguration)
			{
				RegisterTypeIfMissing(typeof(IServiceLocator), typeof(AutofacServiceLocatorAdapter), true);
				RegisterTypeIfMissing(typeof(IModuleInitializer), typeof(ModuleInitializer), true);
				RegisterTypeIfMissing(typeof(IModuleManager), typeof(ModuleManager), true);
				RegisterTypeIfMissing(typeof(RegionAdapterMappings), typeof(RegionAdapterMappings), true);
				RegisterTypeIfMissing(typeof(IRegionManager), typeof(RegionManager), true);
				RegisterTypeIfMissing(typeof(IEventAggregator), typeof(EventAggregator), true);
				RegisterTypeIfMissing(typeof(IRegionViewRegistry), typeof(RegionViewRegistry), true);
				RegisterTypeIfMissing(typeof(IRegionBehaviorFactory), typeof(RegionBehaviorFactory), true);
				RegisterTypeIfMissing(typeof(IRegionNavigationJournalEntry), typeof(RegionNavigationJournalEntry), false);
				RegisterTypeIfMissing(typeof(IRegionNavigationJournal), typeof(RegionNavigationJournal), false);
				RegisterTypeIfMissing(typeof(IRegionNavigationService), typeof(RegionNavigationService), false);
				RegisterTypeIfMissing(typeof(IRegionNavigationContentLoader), typeof(RegionNavigationContentLoader), true);
			}
		}

		/// <summary>
		/// Initializes the modules. May be overwritten in a derived class to use a custom Modules Catalog
		/// </summary>
		protected override void InitializeModules()
		{
			IModuleManager manager;

			try
			{
				manager = Container.Resolve<IModuleManager>();
			}
			catch (DependencyResolutionException ex)
			{
				if (ex.Message.Contains("IModuleCatalog"))
				{
					throw new InvalidOperationException(AutofacBootstrapperResources.NullModuleCatalogException);
				}

				throw;
			}

			manager.Run();
		}

		/// <summary>
		/// Registers a type in the container only if that type was not already registered.
		/// </summary>
		/// <param name="fromType">The interface type to register.</param>
		/// <param name="toType">The type implementing the interface.</param>
		/// <param name="registerAsSingleton">Registers the type as a singleton.</param>
		protected void RegisterTypeIfMissing(Type fromType, Type toType, bool registerAsSingleton)
		{
			if (fromType == null) throw new ArgumentNullException("fromType");
			if (toType == null) throw new ArgumentNullException("toType");

			if (Container.IsRegistered(fromType))
			{
				Logger.Log(string.Format(AutofacBootstrapperResources.TypeMappingAlreadyRegistered, fromType.Name), Category.Debug,
				           Priority.Low);
				return;
			}

			Container.Configure(b =>
			              	{
			              		var registration = b.RegisterType(toType).As(fromType);

			              		if (registerAsSingleton)
			              		{
			              			registration = registration.SingleInstance();
			              		}
			              	});

		}
	}
}